

#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include "xm_cache.h"
#include "xcutil.h"
#include "xm_log.h"

#undef dbgi
#undef dbgw
#undef dbge

#define dbgi(...)
#define dbgw(...)
#define dbge(...)

#define ZERO_ALLOC(o, type, sz) do{o=(type)malloc(sz); memset(o, 0, sz);}while(0)

#define DEFAULT_MAX_CACHES 1280
#define DEFAULT_BUF_SIZE 1600

typedef struct media_data{
    int is_valid;
    uint32_t seq;
    uint8_t *data; //[DEFAULT_BUF_SIZE];
    int data_len;
    int codec;
    int64_t last_time;// in milli-seconds
}media_data;

struct xmcache_st{
    int max_caches;
    media_data * media_datas;
    uint8_t * big_data;
};

static inline
media_data * xmcache_get_data(xmcache_t obj, uint32_t seq){
    return &obj->media_datas[seq%obj->max_caches];
}


void xmcache_delete(xmcache_t obj){
    if(!obj) return;
    if(obj->media_datas){
        free(obj->media_datas);
        obj->media_datas = NULL;
    }
    if(obj->big_data){
        free(obj->big_data);
        obj->big_data = NULL;
    }
    free(obj);
}

xmcache_t xmcache_create(int max_caches, int is_alloc_buf){
    int ret = -1;
    xmcache_t obj = 0;
    do{
        if(max_caches <= 0){
            max_caches = DEFAULT_MAX_CACHES;
        }
        
        ZERO_ALLOC(obj, xmcache_t, sizeof(struct xmcache_st));
        obj->max_caches = max_caches;
        ZERO_ALLOC(obj->media_datas, media_data *, obj->max_caches * sizeof(media_data));
        if(is_alloc_buf){
            ZERO_ALLOC(obj->big_data, uint8_t *, obj->max_caches * DEFAULT_BUF_SIZE);
            for(int i = 0; i < obj->max_caches; i++){
                obj->media_datas[i].data = obj->big_data + i * DEFAULT_BUF_SIZE;
            }
        }
        
        ret = 0;
    }while(0);
    if(ret){
        xmcache_delete(obj);
        obj = 0;
    }
    return obj;
    
}

int xmcache_put_rtp(xmcache_t obj, uint32_t seq, int is_key_frame, int codec, const unsigned char * buf, int length){
    if(length > DEFAULT_BUF_SIZE){
        return -1;
    }
//    uint32_t seq = be_get_u16(buf+2);
    media_data * mdata = xmcache_get_data(obj, seq);
    if(length > 0){
        if(!mdata->data){
            return -2;
        }
        memcpy(mdata->data, buf, length);
    }
    
    mdata->data_len = length;
    mdata->seq = seq;
    mdata->codec = codec;
    mdata->last_time = 0;
    mdata->is_valid = 1;
    return 0;
}

int xmcache_pull_rtp(xmcache_t obj, uint32_t seq, const unsigned char ** pbuf, int *plength){
    media_data * mdata = xmcache_get_data(obj, seq);
    if(!mdata->is_valid || mdata->seq != seq){
        *pbuf = NULL;
        *plength = 0;
        return -1;
    }
    
    *pbuf = mdata->data;
    *plength = mdata->data_len;
    return 0;
}

int xmcache_pull_rtp_range(xmcache_t obj, int64_t now, int64_t max_time
                           , uint16_t orig_pid, uint16_t orig_blp
                           , uint16_t * pnew_pid, uint16_t * pnew_blp
                           , uint32_t * pskip_num
                           , unsigned char ** pbuf, int * plengths, int * pcodecs, int *pnum){
    
    uint16_t new_pid = orig_pid;
    uint16_t new_blp = orig_blp;
    uint32_t skip_num = 0;
    int miss_count = 0;
    int num = 0;
    
    uint16_t seq = orig_pid;
    
    media_data * mdata = xmcache_get_data(obj, seq);
    if(!mdata->is_valid || mdata->seq != seq ){
        new_pid = seq;
        miss_count++;
    }else{
        if(mdata->last_time < max_time){
            pbuf[num] = mdata->data;
            plengths[num] = mdata->data_len;
            pcodecs[num] = mdata->codec;
            num++;
            mdata->last_time = now;
        }else{
//            skip_blp |= 0x01;
            skip_num++;
            dbgi("skip1 seq %u", seq);
        }

    }
    seq++;
    
    uint16_t blp = orig_blp;
    if (blp) {
        for (int i=1; i <= 16; ++i) {
            if (blp & 0x01) {
                
                mdata = xmcache_get_data(obj, seq);
                if(!mdata->is_valid || mdata->seq != seq){
                    if(miss_count == 0){
                        new_pid = seq;
                    }
                    new_blp |= (0x01 << (i-1));
                    miss_count++;
                }else{
                    
                    if(mdata->last_time < max_time){
                        pbuf[num] = mdata->data;
                        plengths[num] = mdata->data_len;
                        pcodecs[num] = mdata->codec;
                        num++;
                        mdata->last_time = now;
                    }else{
//                        skip_blp |= (0x01 << (i));
                        skip_num++;
                        dbgi("skip2 seq %u", seq);
                    }
                    

                }
            }
            blp = blp >>1;
            seq++;
        }
    }
    if(miss_count > 0){
        new_blp >>= (new_pid - orig_pid);
    }
    *pnew_pid = new_pid;
    *pnew_blp = new_blp;
    *pskip_num = skip_num;
    *pnum = num;
    dbgi("rtp cache pid=%u blp=%04X hit=%u miss=%u", orig_pid, orig_blp, num, miss_count);
    return 0;
}

int xmcache_req_range(xmcache_t obj, int64_t now, int64_t max_time
                           , uint16_t orig_pid, uint16_t orig_blp
                           , uint16_t * pnew_pid, uint16_t * pnew_blp
                           , uint32_t * pskip_num
                           , int *pnum){
    
    uint16_t new_pid = 0;
    uint16_t new_blp = 0;
    uint32_t skip_num = 0;
    int miss_count = 0;
    int num = 0;
    
    uint16_t seq = orig_pid;
    
    media_data * mdata = xmcache_get_data(obj, seq);
    if(!mdata->is_valid || mdata->seq != seq || mdata->last_time < max_time){
        new_pid = seq;
        miss_count++;
        mdata->seq = seq;
        mdata->last_time = now;
        dbgi("req-range: req seq %u", seq);
    }else{
        skip_num++;
        dbgi("req-range: skip1 seq %u", seq);
        
    }
    seq++;
    
//    orig_blp = 0xFFFF; // check all seq
    uint16_t blp = orig_blp;

    if (blp) {
        for (int i=1; i <= 16; ++i) {
            if (blp & 0x01) {
                
                mdata = xmcache_get_data(obj, seq);
                if(!mdata->is_valid || mdata->seq != seq || mdata->last_time < max_time){
                    if(miss_count == 0){
                        new_pid = seq;
                    }
                    new_blp |= (0x01 << (i-1));
                    miss_count++;
                    mdata->seq = seq;
                    mdata->last_time = now;
                    dbgi("req-range: req seq %u", seq);
                }else{
                    skip_num++;
                    dbgi("req-range: skip2 seq %u", seq);
                }
            }
            blp = blp >>1;
            seq++;
        }
    }
    if(miss_count > 0){
        new_blp >>= (new_pid - orig_pid);
    }
    *pnew_pid = new_pid;
    *pnew_blp = new_blp;
    *pskip_num = skip_num;
    *pnum = num;
    
    return 0;
}
